﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using LaunchMeNot.Core;
using Microsoft.Win32;

namespace LaunchMeNot.DAL
{
    /// <summary>
    /// Interacts with the registry to obtain values.
    /// </summary>
    public class RegistryStore : IEntryStore {
        /// <summary>
        /// Gets or sets path of the registry key used to store entries.
        /// </summary>
        /// <value>The path of the registry key used to store entries.</value>
        public RegistryKey BaseKey { get; private set; }

        /// <summary>
        /// Gets or sets the type of entries in this store.
        /// </summary>
        /// <value>The type of entries in this store.</value>
        public StartupEntryType EntryType { get; private set; }

        public RegistryStore(StartupEntryType entryType)
        {
            RegistryKey hiveRoot;
            string baseKeyName = LaunchMeNotBLL.GetPath(entryType);
            switch (entryType) {
                case StartupEntryType.CurrentUserRun:
                    hiveRoot = Registry.CurrentUser;
                    break;
                case StartupEntryType.LocalMachineRun:
                    hiveRoot = Registry.LocalMachine;
                    break;
                default:
                    throw new ArgumentException("Only HKLM and HKCU keys are supported by this store.");
            }
            this.EntryType = entryType;
            BaseKey = hiveRoot.OpenSubKey(baseKeyName, true) ?? Registry.LocalMachine.CreateSubKey(baseKeyName);
            if (BaseKey == null) {
                throw new Exception(I18nUtils.GetString("Errors", "CouldNotOpenRegistryKey"));
            }
        }

        /// <summary>
        /// Initializes a new <see cref="RegistryStore"/>.
        /// </summary>
        /// <param name="hive">The hive, must be one of HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_USERS, HKEY_CURRENT_CONFIG.</param>
        /// <param name="baseKeyName">The registry key in which to store entries.</param>
        public RegistryStore(RegistryHive hive, string baseKeyName) {
            RegistryKey hiveRoot;
            switch (hive) {
                case RegistryHive.CurrentUser:
                    hiveRoot = Registry.CurrentUser;
                    break;
                case RegistryHive.LocalMachine:
                    hiveRoot = Registry.LocalMachine;
                    break;
                case RegistryHive.ClassesRoot:
                    hiveRoot = Registry.ClassesRoot;
                    break;
                case RegistryHive.CurrentConfig:
                    hiveRoot = Registry.CurrentConfig;
                    break;
                case RegistryHive.DynData:
                    hiveRoot = Registry.DynData;
                    break;
                case RegistryHive.PerformanceData:
                    hiveRoot = Registry.PerformanceData;
                    break;
                case RegistryHive.Users:
                    hiveRoot = Registry.Users;
                    break;
                default:
                    throw new ArgumentException("Invalid hive");
            }
            BaseKey = hiveRoot.OpenSubKey(baseKeyName, true) ?? Registry.LocalMachine.CreateSubKey(baseKeyName);
            if (BaseKey == null) {
                throw new Exception(I18nUtils.GetString("Errors", "CouldNotOpenRegistryKey"));
            }
        }

        #region CRUD
        /// <summary>
        /// Adds a new startup entry.
        /// </summary>
        /// <param name="entry">The entry.</param>
        public void AddStartupEntry(IStartupEntry entry)
        {
            if (BaseKey.GetValue(entry.Name, null) != null)
                throw new LaunchMeNotException(650, I18nUtils.GetString("Errors", "EntryNameExistsFmt", entry.Name));
            AddStartupEntries(new[] { entry });
        }

        /// <summary>
        /// Adds multiple new startup entries.
        /// </summary>
        /// <param name="entries">The entries.</param>
        public void AddStartupEntries(IEnumerable<IStartupEntry> entries) {
            var realEntries = entries.Cast<RegistryShortcut>();
            RegistryShortcut firstExistingEntry = realEntries.FirstOrDefault(e => BaseKey.GetValue(e.Name) != null);
            if (firstExistingEntry != null)
                throw new LaunchMeNotException(650, I18nUtils.GetString("Errors", "EntryNameExistsFmt", firstExistingEntry.Name));
            foreach (var entry in realEntries) {
                BaseKey.SetValue(entry.Name, entry.SafeFullPath, RegistryValueKind.String);
            }
        }

        /// <summary>
        /// Edits a startup entry.
        /// </summary>
        /// <param name="entry">The entry.</param>
        public void EditStartupEntry(IStartupEntry entry)
        {
            RegistryShortcut realEntry = entry as RegistryShortcut;
            if (realEntry == null)
                throw new ArgumentException("Only Registry Entries can be stored here.");
            if (!string.IsNullOrEmpty(realEntry.OriginalName) && realEntry.OriginalName != realEntry.Name) {
                // name has changed, validate the new name first
                object firstExistingEntry = BaseKey.GetValue(entry.Name);
                if(firstExistingEntry != null)
                    throw new LaunchMeNotException(650, I18nUtils.GetString("Errors", "EntryNameExistsFmt", Convert.ToString(firstExistingEntry)));
                // new name's Ok, delete the original key
                BaseKey.DeleteValue(realEntry.OriginalName);
            }
            BaseKey.SetValue(entry.Name, realEntry.SafeFullPath, RegistryValueKind.String);
        }

        /// <summary>
        /// Deletes a startup entry.
        /// </summary>
        /// <param name="entry">The entry.</param>
        public void DeleteStartupEntry(IStartupEntry entry)
        {
            DeleteStartupEntries(new[] { entry });
        }

        /// <summary>
        /// Deletes multiple startup entries.
        /// </summary>
        /// <param name="entries">The entries.</param>
        public void DeleteStartupEntries(IEnumerable<IStartupEntry> entries) {
            foreach (IStartupEntry entry in entries) {
                BaseKey.DeleteValue(entry.Name, false);
            }
        }

        /// <summary>
        /// Gets a list of all startup entries managed by LaunchMeNot.
        /// </summary>
        /// <returns>All startup entries managed by LaunchMeNot.</returns>
        public IList<IStartupEntry> GetStartupEntries()
        {
            List<IStartupEntry> entries = new List<IStartupEntry>();
            string[] items = BaseKey.GetValueNames();
            foreach (string entryName in items) {
                if (entryName.Trim().Length == 0)
                    continue;
                string path = Convert.ToString(BaseKey.GetValue(entryName));
                if (!string.IsNullOrEmpty(path)) {
                    var pair = Utils.SplitArguments(path);
                    entries.Add(new RegistryShortcut(entryName, pair.Key, pair.Value) { EntryType = this.EntryType });
                }
            }
            return entries;
        }
        #endregion
    }
}